코로나 바이러스(COVID-19) EDA

  • 코로나 바이러스란
  • 세계 속 코로나 바이러스
  • 확진자 예측
  • 지하철과 버스로 알아본 유동인구 변화
  • 대한민국의 코로나 바이러스
  • 국내 확진자 정보

<코로나 바이러스>

  • 2019년 12월 중국 우한에서 처음 발생한 이후 중국 전역과 전 세계로 확산된, 새로운 유형의 코로나바이러스(SARS-CoV-2)에 의한 호흡기 감염질환이다.
  • 코로나바이러스감염증-19는 감염자의 비말(침방울)이 호흡기나 눈·코·입의 점막으로 침투될 때 전염된다. 감염되면 약 2~14일(추정)의 잠복기를 거친 뒤 발열(37.5도) 및 기침이나 호흡곤란 등 호흡기 증상, 폐렴이 주증상으로 나타나지만 무증상 감염 사례도 드물게 나오고 있다. [네이버 지식백과] 코로나바이러스감염증-19 - (COVID-19) (시사상식사전, pmg 지식엔진연구소) 사진

<세계 속 코로나 바이러스>

라이브러리 불러오기

  • numpy, pandas로 전처리 및 분석
  • seaborn과 matplot, plotly, calmap으로 시각화
  • 예측은 fbprophet을 사용
In [75]:
import numpy as np
import pandas as pd 

import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import plotly.express as px
import plotly.graph_objs as go
import plotly.figure_factory as ff
import plotly.offline as py
import calmap
from IPython.display import set_matplotlib_formats

from datetime import date, timedelta
from fbprophet import Prophet
from fbprophet.plot import plot_plotly, add_changepoints_to_plot
from scipy.special import inv_boxcox

# 경고 숨기기
import warnings
warnings.filterwarnings('ignore')

# 컬러
cnfcol = '#393e46' # 확진자 - 회색
dthcol = '#ff2e63' # 사망자 - 빨간색
reccol = '#21bf73' # 회복자 - 청록색
actcol = '#fe9801' # 대기자 - 노란색

# 한글 폰트 및 레티나 설정
plt.rc("font" , family = "Malgun Gothic")
plt.rc("axes" , unicode_minus = False)
set_matplotlib_formats('retina')

# 지도시각화
import requests
from bs4 import BeautifulSoup as bs
import json
from pandas.io.json import json_normalize
import os

# 코드 숨기기
from IPython.display import display
from IPython.display import HTML
import IPython.core.display as di
di.display_html('<script>jQuery(function() {if (jQuery("body.notebook_app").length == 0) { jQuery(".input_area").toggle(); jQuery(".prompt").toggle();}});</script>', raw=True)

데이터 불러오기

  • 데이터는 캐글의 질병관리본부 데이터와 세계 코로나바이러스-19 데이터를 활용함
  • 사망자 정보는 네이버를 참고함
In [2]:
full_table = pd.read_csv('data/covid_19_clean_complete.csv', parse_dates=['Date'])
# cases 
cases = ['Confirmed', 'Deaths', 'Recovered', 'Active']

# Active Case = confirmed - deaths - recovered
full_table['Active'] = full_table['Confirmed'] - full_table['Deaths'] - full_table['Recovered']

# replacing Mainland china with just China
full_table['Country/Region'] = full_table['Country/Region'].replace('Mainland China', 'China')

# filling missing values 
full_table[['Province/State']] = full_table[['Province/State']].fillna('')
full_table[cases] = full_table[cases].fillna(0)
# latest
full_latest = full_table[full_table['Date'] == max(full_table['Date'])].reset_index()
row_latest = full_latest[full_latest['Country/Region']!='China']

# latest condensed
full_latest_grouped = full_latest.groupby('Country/Region')['Confirmed', 'Deaths', 'Recovered', 'Active'].sum().reset_index()
row_latest_grouped = row_latest.groupby('Country/Region')['Confirmed', 'Deaths', 'Recovered', 'Active'].sum().reset_index()
temp = full_table.groupby(['Country/Region', 'Province/State'])['Confirmed', 'Deaths', 'Recovered', 'Active'].max()
# temp.style.background_gradient(cmap='Reds')

세계 현황(2020-03-17 00시 기준)

  • 누적 확진자 : 197,146명
  • 사망자 : 7,905명
  • 격리해제자 : 80,840명
  • 확진자 : 108,401명
In [3]:
temp = full_table.groupby('Date')['Confirmed', 'Deaths', 'Recovered', 'Active'].sum().reset_index()
temp = temp[temp['Date']==max(temp['Date'])].reset_index(drop=True)
temp.style.background_gradient(cmap='Pastel1')
Out[3]:
Date Confirmed Deaths Recovered Active
0 2020-03-17 00:00:00 197146 7905 80840 108401

트리맵

In [4]:
tm = temp.melt(id_vars="Date", value_vars=['Active', 'Deaths', 'Recovered'])
fig = px.treemap(tm, path=["variable"], values="value", height=400, width=600,
                 color_discrete_sequence=[reccol, actcol, dthcol])
fig.show()

확진자 그래프

In [5]:
flg = full_latest_grouped
fig = px.bar(flg.sort_values('Confirmed', ascending=False).head(20).sort_values('Confirmed', ascending=True), 
             x="Confirmed", y="Country/Region", title='누적 확진자 수', text='Confirmed', orientation='h', 
             width=700, height=700, range_x = [0, max(flg['Confirmed'])+10000])
fig.update_traces(marker_color='#46cdcf', opacity=0.8, textposition='outside')
fig.show()

국가별 현황

  • 확진자 수 부동의 1위는 중국이다. 시간이 지나 격리해제가 많이 된 상태이다. 환자는 9,030명이다.
  • 이탈리아는 확진자 대비 사망자가 많다. 환자는 26,062명으로 세계에서 가장 많다.
  • 그 다음으로 확진자 수가 많은 국가는 이란, 스페인, 독일이다.
  • 한국은 6위를 기록하고 있으며, 환자는 6,832명이다.
In [6]:
temp_f = full_latest_grouped.sort_values(by='Confirmed', ascending=False)
temp_f = temp_f.reset_index(drop=True)
temp_f.style.background_gradient(cmap='Reds')
Out[6]:
Country/Region Confirmed Deaths Recovered Active
0 China 81058 3230 68798 9030
1 Italy 31506 2503 2941 26062
2 Iran 16169 988 5389 9792
3 Spain 11748 533 1028 10187
4 Germany 9257 24 67 9166
5 Korea, South 8320 81 1407 6832
6 France 7699 148 12 7539
7 US 6421 108 17 6296
8 Switzerland 2700 27 4 2669
9 United Kingdom 1960 56 53 1851
10 Netherlands 1708 43 2 1663
11 Norway 1463 3 1 1459
12 Austria 1332 3 1 1328
13 Belgium 1243 10 1 1232
14 Sweden 1190 7 1 1182
15 Denmark 1024 4 1 1019
16 Japan 878 29 144 705
17 Cruise Ship 696 7 325 364
18 Malaysia 673 2 49 622
19 Canada 478 5 9 464
20 Australia 452 5 23 424
21 Portugal 448 1 3 444
22 Qatar 439 0 4 435
23 Czechia 396 0 3 393
24 Greece 387 5 8 374
25 Israel 337 0 11 326
26 Brazil 321 1 2 318
27 Finland 321 0 10 311
28 Slovenia 275 1 0 274
29 Singapore 266 0 114 152
30 Poland 238 5 13 220
31 Pakistan 236 0 2 234
32 Bahrain 228 1 81 146
33 Estonia 225 0 1 224
34 Ireland 223 2 5 216
35 Iceland 220 1 0 219
36 Chile 201 0 0 201
37 Egypt 196 4 32 160
38 Philippines 187 12 5 170
39 Romania 184 0 16 168
40 Thailand 177 1 41 135
41 Indonesia 172 5 8 159
42 Saudi Arabia 171 0 6 165
43 Iraq 154 11 32 111
44 India 142 3 14 125
45 Luxembourg 140 1 0 139
46 Kuwait 130 0 9 121
47 Lebanon 120 3 3 114
48 Peru 117 0 1 116
49 Russia 114 0 8 106
50 San Marino 109 7 4 98
51 United Arab Emirates 98 0 23 75
52 Mexico 82 0 4 78
53 Armenia 78 0 1 77
54 Taiwan* 77 1 22 54
55 Slovakia 72 0 0 72
56 Panama 69 1 0 68
57 Argentina 68 2 3 63
58 Bulgaria 67 2 0 65
59 Vietnam 66 0 16 50
60 Colombia 65 0 1 64
61 Croatia 65 0 4 61
62 Serbia 65 0 1 64
63 South Africa 62 0 0 62
64 Algeria 60 4 12 44
65 Ecuador 58 2 0 56
66 Brunei 56 0 0 56
67 Albania 55 1 0 54
68 Hungary 50 1 2 47
69 Latvia 49 0 1 48
70 Turkey 47 1 0 46
71 Cyprus 46 0 0 46
72 Sri Lanka 44 0 1 43
73 Costa Rica 41 0 0 41
74 Andorra 39 0 1 38
75 Morocco 38 2 1 35
76 Malta 38 0 2 36
77 Belarus 36 0 3 33
78 Georgia 34 0 1 33
79 Jordan 34 0 1 33
80 Cambodia 33 0 1 32
81 Venezuela 33 0 0 33
82 Kazakhstan 33 0 0 33
83 Moldova 30 0 1 29
84 Uruguay 29 0 0 29
85 Azerbaijan 28 1 6 21
86 Senegal 26 0 2 24
87 North Macedonia 26 0 1 25
88 Bosnia and Herzegovina 26 0 2 24
89 Lithuania 25 0 1 24
90 Oman 24 0 9 15
91 Tunisia 24 0 0 24
92 Afghanistan 22 0 1 21
93 Dominican Republic 21 1 0 20
94 Martinique 16 1 0 15
95 Burkina Faso 15 0 0 15
96 Ukraine 14 2 0 12
97 Maldives 13 0 0 13
98 Jamaica 12 0 2 10
99 New Zealand 12 0 0 12
100 Bolivia 11 0 0 11
101 Uzbekistan 10 0 0 10
102 Bangladesh 10 0 3 7
103 Cameroon 10 0 0 10
104 Paraguay 9 0 0 9
105 Honduras 8 0 0 8
106 Ghana 7 0 0 7
107 Guyana 7 1 0 6
108 Monaco 7 0 0 7
109 Rwanda 7 0 0 7
110 Liechtenstein 7 0 0 7
111 Guatemala 6 1 0 5
112 Cote d'Ivoire 5 0 1 4
113 Cuba 5 0 0 5
114 Mongolia 5 0 0 5
115 Trinidad and Tobago 5 0 0 5
116 Ethiopia 5 0 0 5
117 Seychelles 4 0 0 4
118 Kenya 3 0 0 3
119 Aruba 3 0 0 3
120 Nigeria 3 0 0 3
121 Congo (Kinshasa) 3 0 0 3
122 Jersey 2 0 0 2
123 Montenegro 2 0 0 2
124 Barbados 2 0 0 2
125 Namibia 2 0 0 2
126 Kosovo 2 0 0 2
127 Saint Lucia 2 0 0 2
128 Holy See 1 0 0 1
129 Somalia 1 0 0 1
130 Antigua and Barbuda 1 0 0 1
131 Greenland 1 0 0 1
132 Guernsey 1 0 0 1
133 Mauritania 1 0 0 1
134 Gabon 1 0 0 1
135 Republic of the Congo 1 0 0 1
136 Benin 1 0 0 1
137 Bhutan 1 0 0 1
138 Nepal 1 0 1 0
139 Eswatini 1 0 0 1
140 Togo 1 0 0 1
141 The Gambia 1 0 0 1
142 The Bahamas 1 0 0 1
143 Liberia 1 0 0 1
144 Tanzania 1 0 0 1
145 Equatorial Guinea 1 0 0 1
146 Saint Vincent and the Grenadines 1 0 0 1
147 Congo (Brazzaville) 1 0 0 1
148 Suriname 1 0 0 1
149 Sudan 1 1 0 0
150 Guinea 1 0 0 1
151 Central African Republic 1 0 0 1

사망자가 많은 상위 10개 국가

  • 확진자 수와 사망자 수는 비례하지 않는다.
  • 사망자 수를 나타내보았더니, 프랑스와 미국은 한국보다 확진자가 적음에도 불구하고 사망자가 많다.
In [7]:
temp_flg = temp_f[temp_f['Deaths']>0][['Country/Region', 'Deaths']]
temp_flg.sort_values('Deaths', ascending=False).reset_index(drop=True).head(10).style.background_gradient(cmap='Reds')
Out[7]:
Country/Region Deaths
0 China 3230
1 Italy 2503
2 Iran 988
3 Spain 533
4 France 148
5 US 108
6 Korea, South 81
7 United Kingdom 56
8 Netherlands 43
9 Japan 29

사망자 그래프

In [8]:
fig = px.bar(flg.sort_values('Deaths', ascending=False).head(10).sort_values('Deaths', ascending=True), 
             x="Deaths", y="Country/Region", title='누적 사망자 수', text='Deaths', orientation='h', 
             width=700, height=700, range_x = [0, max(flg['Deaths'])+500])
fig.update_traces(marker_color=dthcol, opacity=0.6, textposition='outside')
fig.show()

격리해제가 없는 국가

  • 격리해제가 없는 국가를 알아보았다.
  • 확진자 수가 대체로 적은 것으로 보아, 코로나 바이러스의 영향을 최근에 받은 것 같다.
In [9]:
temp = temp_f[temp_f['Recovered']==0][['Country/Region', 'Confirmed', 'Deaths', 'Recovered']]
temp.head(10).reset_index(drop=True).style.background_gradient(cmap='Reds')
Out[9]:
Country/Region Confirmed Deaths Recovered
0 Slovenia 275 1 0
1 Iceland 220 1 0
2 Chile 201 0 0
3 Luxembourg 140 1 0
4 Slovakia 72 0 0
5 Panama 69 1 0
6 Bulgaria 67 2 0
7 South Africa 62 0 0
8 Ecuador 58 2 0
9 Brunei 56 0 0

모든 확진자가 사망한 국가

  • 모든 확진자가 사망한 국가를 알아보았다.
  • Sudan이라는 국가로, 확진자 1명이 사망하였다.
In [10]:
temp = row_latest_grouped[row_latest_grouped['Confirmed']==
                          row_latest_grouped['Deaths']]
temp = temp[['Country/Region', 'Confirmed', 'Deaths']]
temp = temp.sort_values('Confirmed', ascending=False)
temp = temp.reset_index(drop=True)
temp.style.background_gradient(cmap='Reds')
Out[10]:
Country/Region Confirmed Deaths
0 Sudan 1 1

모든 확진자가 회복된 국가

  • 모든 확진자가 회복된 국가를 알아보았다.
  • Nepal이라는 국가로, 확진자 1명이 회복되었다.
In [11]:
temp = row_latest_grouped[row_latest_grouped['Confirmed']==
                          row_latest_grouped['Recovered']]
temp = temp[['Country/Region', 'Confirmed', 'Recovered']]
temp = temp.sort_values('Confirmed', ascending=False)
temp = temp.reset_index(drop=True)
temp.style.background_gradient(cmap='Greens')
Out[11]:
Country/Region Confirmed Recovered
0 Nepal 1 1

회복자 그래프

In [12]:
fig = px.bar(flg.sort_values('Recovered', ascending=False).head(10).sort_values('Recovered', ascending=True), 
             x="Recovered", y="Country/Region", title='누적 격리해제 수', text='Recovered', orientation='h', 
             width=700, height=700, range_x = [0, max(flg['Recovered'])+10000])
fig.update_traces(marker_color=reccol, opacity=0.6, textposition='outside')
fig.show()

코로나 바이러스 환자 그래프

In [13]:
fig = px.bar(flg.sort_values('Active', ascending=False).head(20).sort_values('Active', ascending=True), 
             x="Active", y="Country/Region", title='현재 환자 수', text='Active', orientation='h', 
             width=700, height=700, range_x = [0, max(flg['Active'])+3000])
fig.update_traces(marker_color='#f0134d', opacity=0.6, textposition='outside')
fig.show()

치사율 그래프

  • 이탈리아가 가장 높은 치사율을 보이고 있다.
  • 한국(0.97%)의 8배, 세계보건기구(WHO) 평균(3.4%)과 중국(3.98%)의 2배 수준이다.
  • 세계 최고의 치사율은 보이는데 몇가지 추론을 할 수 있다.
  • 코로나 바이러스 사망자 대부분이 고령자인데 작년 기준 이탈리아의 65세 이상 고령 인구 비중은 23%로 세계에서 일본(28.4%)에 이어 두 번째로 높다.
  • 이탈리아의 14일 기준 누적 검사자 수는 10만9천170명으로 한국(26만50명)의 42%에 불과하다. 한국처럼 전방위적인 검사를 할 경우 누적 확진자 수가 크게 불어 치명률이 자연스럽게 WHO가 추산한 세계 평균(3.4%)에 근접할 수 있다는 추론도 가능하다.
  • 한국의 치사율은 세계에서 23번째로 높다.
  • 확진자 수는 10개 국가 안에 들어가는데 치사율은 낮은 편이다.
In [14]:
# (Only countries with more than 100 case are considered)

flg['Mortality Rate'] = round((flg['Deaths']/flg['Confirmed'])*100, 2)
temp = flg[flg['Confirmed']>100]
temp = temp.sort_values('Mortality Rate', ascending=False)

fig = px.bar(temp.sort_values('Mortality Rate', ascending=False).head(23).sort_values('Mortality Rate', ascending=True), 
             x="Mortality Rate", y="Country/Region", text='Mortality Rate', orientation='h', 
             width=700, height=600, range_x = [0, 10], title='치사율')
fig.update_traces(marker_color='#00a8cc', opacity=0.6, textposition='outside')
fig.show()

한편, 한국 사망자의 평균 연령은 75세이다.

In [15]:
import math
dead = pd.read_csv('data/deaths.csv')
dead = dead.fillna(0)
math.floor(np.mean(dead['나이']))
Out[15]:
75

<확진자 예측>

In [16]:
df_predict = pd.read_csv("data/time_series_19-covid-Confirmed_3.csv")
df_korea = df_predict[df_predict["Country/Region"] == "South Korea"]
df_korea.head()
Out[16]:
Province/State Country/Region Lat Long 2020-01-22 2020-01-23 2020-01-24 2020-01-25 2020-01-26 2020-01-27 ... 2020-03-10 2020-03-11 2020-03-12 2020-03-13 2020-03-14 2020-03-15 2020-03-16 2020-03-17 2020-03-18 2020-03-19
33 NaN South Korea 37.5665 126.978 1 1 2 2 3 4 ... 7513.0 7755.0 7869.0 7979.0 NaN NaN NaN NaN NaN NaN

1 rows × 62 columns

In [17]:
df_korea = df_korea.T[4:]
df_korea = df_korea.reset_index().rename(columns = {'index':'date' ,33:'confirmed'})
df_korea['date'] = pd.to_datetime(df_korea['date'])
df_korea.head()
Out[17]:
date confirmed
0 2020-01-22 1
1 2020-01-23 1
2 2020-01-24 2
3 2020-01-25 2
4 2020-01-26 3
In [18]:
df_prophet = df_korea.rename(columns = {
    'date' :'ds',
    'confirmed' : 'y'
})

df_prophet.head()
Out[18]:
ds y
0 2020-01-22 1
1 2020-01-23 1
2 2020-01-24 2
3 2020-01-25 2
4 2020-01-26 3
In [19]:
m = Prophet(
    changepoint_prior_scale = 0.2, #값이 커질수록 모델을 유연하게
    changepoint_range = 0.98,  #데이터 앞쪽 몇퍼센트 부분에서 변곡점을 만들것인지 설정
    yearly_seasonality = False,
    weekly_seasonality = False,
    daily_seasonality = True,
    #additive - 계절변동의 영향이 트렌드에 더해지는 형태로 나타날때 사용
    seasonality_mode = 'additive'
)
m.fit(df_prophet)
future = m.make_future_dataframe(periods = 7)
future.tail(7)
Out[19]:
ds
58 2020-03-20
59 2020-03-21
60 2020-03-22
61 2020-03-23
62 2020-03-24
63 2020-03-25
64 2020-03-26
In [20]:
forecast = m.predict(future)
forecast[['ds','yhat','yhat_lower','yhat_upper']].tail(7)
#ds날짜 , yhat예측값 
#yhat_lower 오차를 고려한 예측최솟값 , yhat_upper - 오차를 고려한 예측 최댓값
Out[20]:
ds yhat yhat_lower yhat_upper
58 2020-03-20 8946.734396 8338.766639 9475.760996
59 2020-03-21 9082.496205 8330.161253 9722.228741
60 2020-03-22 9218.258014 8296.810100 9985.713562
61 2020-03-23 9354.019823 8290.725643 10254.101833
62 2020-03-24 9489.781632 8214.706147 10566.667306
63 2020-03-25 9625.543440 8168.542347 10858.442218
64 2020-03-26 9761.305249 8129.467588 11189.815832
In [21]:
fig = plot_plotly(m,forecast)

py.iplot(fig)
In [22]:
df_check = pd.read_csv("data/검사대비확진비율.csv")
df_cum = pd.read_csv("data/일별확진자.csv")
df_check['일자'] = df_check["일자"].astype(str).map(lambda x : x[5:])
df_cum = df_cum.reset_index()
df_cum['일자'] = df_cum["일자"].astype(str).map(lambda x : x[5:])
df_check = df_check.set_index("일자")
df_cum = df_cum.set_index("일자")
print(df_check.head(5))
print(df_cum.head(5))
         음성판정수     확진률
일자                    
 2. 10.    585  0.0034
 2. 11.    796  0.0013
 2. 12.   1318  0.0000
 2. 13.   1045  0.0000
 2. 14.   1035  0.0000
         index  누적확진자  발생확진자
일자                          
 2. 10.      0     27      2
 2. 11.      1     28      1
 2. 12.      2     28      0
 2. 13.      3     28      0
 2. 14.      4     28      0

일자별 확진자 그래프(point = 누적확진자 , bar = 일일확진자)

In [23]:
df_dates = pd.read_csv('data/covid19_dates.csv')
df_dates = df_dates.T
df_dates = df_dates.reset_index()
df_dates.columns = ['dates','variation','confirmed']
df_dates = df_dates.drop(0)
plt.figure(figsize=(20,8))
plt.xticks(rotation=90)
sns.pointplot(data=df_dates, x='dates', y='confirmed')
g = sns.barplot(data=df_dates, x='dates', y='variation')
for i, s in enumerate(df_dates['confirmed']) :
    if s > 1000 :
        g.text(x=i-1, y=s+450, s=s)

일별 확진자와 누적 확진자

  • 2월 29일 하루 가장 많은 확진자(909명)가 생겼다.
  • 확진자 수가 증가한다는 의미는 검사 수와 음성판정 수가 증가한다는 의미로 일자 별 검사대비 확진비율을 살펴 보겠다. $$확진비율 = {확진자수 \over 확진자수 + 음성 판정수}$$
In [24]:
fig , ax0 = plt.subplots(figsize=(20, 5))
ax1 = ax0.twinx()
ax0.set_title("일별 확진자추이")
ax0.plot(df_cum["누적확진자"] ,'r-', label = "누적확진자" )
ax0.set_ylabel("누적확진자")
ax0.grid(False)
ax1.plot(df_cum["발생확진자"] ,'g:', label ="발생확진자")
ax1.set_ylabel("발생확진자")
ax1.grid(False)
ax0.set_xlabel("날짜")
fig.legend()
plt.show()
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.

검사대비 확진비율

  • 2월 18일 31번 확진자가 확진을 받은 이후 신천지 전수조사로 인해 확진율이 크게 증가한 것으로 보임
  • 3월 1일 기준으로 검사 대비 확진 비율이 감소하는 것으로 보아, 사회적 거리두기의 효과가 있는 것으로 보임
In [25]:
fig , ax0 = plt.subplots(figsize=(20, 5))
ax1 = ax0.twinx()
ax0.set_title("검사대비 확진비율")
ax0.plot(df_check["음성판정수"] ,'r-', label = "음성판정수" )
ax0.set_ylabel("음성판정수")
ax0.grid(False)
ax1.plot(df_check["확진률"] ,'g:', label ="확진률")
ax1.set_ylabel("확진률")
ax1.grid(False)
ax0.set_xlabel("날짜")
fig.legend()
plt.show()
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.
INFO:matplotlib.category:Using categorical units to plot a list of strings that are all parsable as floats or dates. If these strings should be plotted as numbers, cast to the appropriate data type before plotting.

<지하철과 버스로 알아본 유동인구 변화>

유/무임 승차 인원

In [26]:
# 데이터를 불러옵니다. 
sub = pd.read_csv("./data/서울시 지하철 호선별 역별 유_무임 승하차 인원 정보.csv", encoding='cp949')
# 작년 12월 ~ 2월과 추이를 비교하기 위해서 그 이전 데이터는 제거합니다.  
drop_index = sub[sub['사용월'] < 201811].index
sub.drop(drop_index, inplace=True)
# 필요없는 컬럼은 제거합니다. 
sub = sub.drop(['작업일자'], axis=1).copy()
sub.groupby(['호선명'])['유임승차인원'].median().sort_values(ascending=False).plot.barh(figsize=(15, 7), color='green')
sub.groupby(['호선명'])['무임승차인원'].median().sort_values(ascending=False).plot.barh(figsize=(15, 7))
Out[26]:
<matplotlib.axes._subplots.AxesSubplot at 0x181e9ab3b38>
In [27]:
# 호선명에서 유/무임 승차인원이 많은 노선 10개만 line_df 에 담아줍니다. 
# df['호선명'].unique()

pay_index = sub.groupby(['호선명'])['유임승차인원'].median().sort_values(ascending=False).head(15).index
# print(pay_index)
nonpay_index = sub.groupby(['호선명'])['무임승차인원'].median().sort_values(ascending=False).head(15).index
# print(nonpay_index)

line_list = []
for element in pay_index:
    if element in nonpay_index:
        line_list.append(element)
print(line_list)

line_df = sub[sub['호선명'].isin(line_list)]
['2호선', '4호선', '1호선', '7호선', '3호선', '경인선', '과천선', '분당선', '8호선', '경부선', '6호선', '일산선', '5호선', '9호선']
In [28]:
# 지하철역에서 유/무임 승차인원이 많은 역을 station_df 에 담아줍니다. 
# line_df['지하철역'].nunique()

pay_index = line_df.groupby(['지하철역'])['유임승차인원'].median().sort_values(ascending=False).head(30).index
# print(pay_index)
nonpay_index = line_df.groupby(['지하철역'])['무임승차인원'].median().sort_values(ascending=False).head(30).index
# print(nonpay_index)

station_list = []
for element in pay_index:
    if element in nonpay_index:
        station_list.append(element)
print(station_list)

station_df = line_df[line_df['지하철역'].isin(station_list)]
['강남', '신림', '구로디지털단지', '서울대입구(관악구청)', '역삼', '영등포', '강변(동서울터미널)', '종각', '양재(서초구청)', '부천', '부평', '수유(강북구청)', '압구정', '쌍문', '역곡']
In [29]:
station_df.groupby(['지하철역'])['유임승차인원'].median().sort_values(ascending=False).plot.barh(figsize=(20, 7), color='green')
station_df.groupby(['지하철역'])['무임승차인원'].median().sort_values(ascending=False).plot.barh(figsize=(20, 7))
Out[29]:
<matplotlib.axes._subplots.AxesSubplot at 0x181e9be50f0>
In [30]:
# 하나의 데이터 셋에 승차인원과 유/무임 여부를 정리해줍니다. 
pay = station_df[['사용월', '호선명', '지하철역', '유임승차인원']]
pay['유임/무임'] = '유임'
pay = pay.rename(columns = {'유임승차인원' : '승차인원'})

npay = station_df[['사용월', '호선명', '지하철역', '무임승차인원']]
npay['유임/무임'] = '무임'
npay = npay.rename(columns = {'무임승차인원' : '승차인원'})

df_station = pd.concat([pay, npay])
df_station.head()
Out[30]:
사용월 호선명 지하철역 승차인원 유임/무임
151 202002 경인선 역곡 579132 유임
153 202002 경인선 부천 699027 유임
154 202002 경인선 부평 726378 유임
237 202002 경부선 영등포 880993 유임
453 202002 1호선 종각 872537 유임
In [31]:
plt.figure(figsize=(20, 7))
plt.axvline(x=2.5, ymin=0, ymax=1, linewidth=3, color='red')
plt.axvline(x=14.5, ymin=0, ymax=1, linewidth=3, color='red')
# plt.axvline(x=14.8, ymin=0, ymax=1, linewidth=3, color='green')
sns.pointplot(data=df_station, x='사용월', y='승차인원', hue='유임/무임', ci=None)
Out[31]:
<matplotlib.axes._subplots.AxesSubplot at 0x181e9ad9978>

버스 승하차 인원

In [32]:
# 데이터를 불러옵니다.
# 12월
bus1912 = pd.read_csv('./data/BUS_STATION_BOARDING_MONTH_201912_1.csv', encoding='cp949')
bus1912_0 = bus1912[['사용일자', '역명', '승차총승객수', '하차총승객수']]
# bus1912_0.tail() # 20191201 ~ 20191231
# 1월
bus2001 = pd.read_csv('./data/BUS_STATION_BOARDING_MONTH_202001.csv', encoding='cp949')
bus2001_0 = bus2001[['사용일자', '역명', '승차총승객수', '하차총승객수']]
# bus2001_0.tail() # 20200101 ~ 20200131
# 2월
bus2002 = pd.read_csv('./data/BUS_STATION_BOARDING_MONTH_202002.csv', encoding='cp949')
bus2002_0 = bus2002[['사용일자', '역명', '승차총승객수', '하차총승객수']]
# bus2002_0.tail() # 20200201 ~ 20200229
In [33]:
# 3월
bus2003 = pd.read_csv('./data/서울시 버스노선별 정류장별 승하차 인원 정보.csv', encoding='cp949')
# bus2003.head()
bus2003_0 = bus2003[bus2003['사용일자'] > 20200229].sort_values(by='사용일자').reset_index().drop('index', axis=1)
bus2003_0 = bus2003_0[['사용일자', '역명', '승차총승객수', '하차총승객수']].copy()
# bus2003_0.tail() # 20200301 ~ 20200315
In [34]:
# 12월부터 3월까지 데이터를 합쳐줍니다. 
bus = pd.concat([bus1912_0, bus2001_0, bus2002_0, bus2003_0])
bus.head()
Out[34]:
사용일자 역명 승차총승객수 하차총승객수
0 20191201 한성여객종점 13 0
1 20191201 공군회관 109 75
2 20191201 서울지방병무청앞 71 68
3 20191201 강남중학교 108 86
4 20191201 서울공업고등학교 165 94
In [35]:
# 승/하차인원이 많은 역만 선별해 station_df 에 담아줍니다. 
on_index = bus.groupby(['역명'])['승차총승객수'].median().sort_values(ascending=False).head(15).index
off_index = bus.groupby(['역명'])['하차총승객수'].median().sort_values(ascending=False).head(15).index

station_list = []
for element in on_index:
    if element in off_index:
        station_list.append(element)
print(station_list)

station_df = bus[bus['역명'].isin(station_list)]
['당산역.지하철2호선', '신당역하나은행', '신림역', '신림역4번출구', '경희대의료원', '회기역종점', '서울대입구역2호선', '강변역A', '오목교역.대학학원']
In [36]:
# 히트맵으로 일별 승차인원 변화를 봅니다.
pivot = station_df.pivot_table(index='사용일자', columns='역명', values='승차총승객수')
plt.figure(figsize=(8, 30))
sns.heatmap(data=pivot, annot=False, fmt='.0f', linewidths=5, cmap='Blues')
Out[36]:
<matplotlib.axes._subplots.AxesSubplot at 0x181fcce0dd8>
In [37]:
on = station_df[['사용일자', '역명', '승차총승객수']]
on['승하차여부'] = '승차'
on = on.rename(columns = {'승차총승객수' : '승하차인원'})

off = station_df[['사용일자', '역명', '하차총승객수']]
off['승하차여부'] = '하차'
off = off.rename(columns = {'하차총승객수' : '승하차인원'})

df_fin = pd.concat([on, off])
In [38]:
# 일별 승/하차인원 변화를 봅니다. 
plt.figure(figsize=(20, 7))
plt.xticks(rotation=90)

plt.axvline(x=49, ymin=0, ymax=1, linewidth=3, color='red') # 1월19일 (한국 코로나 발발)
plt.axvline(x=89, ymin=0, ymax=1, linewidth=3, color='green') # 2월28일 ('사회적 거리두기')
sns.pointplot(data=df_fin, x='사용일자', y='승하차인원', hue='승하차여부', ci=None)
Out[38]:
<matplotlib.axes._subplots.AxesSubplot at 0x181e87ed9e8>

<대한민국의 코로나 바이러스>

대한민국 현황(2020-03-18 00시 기준)

  • 누적 확진자 : 8,413명
  • 사망자 : 91명
  • 격리해제자 : 1,540명
  • 확진자 : 8,413명
In [39]:
korea = pd.read_csv('data/covid19_korea.csv')
group = korea.groupby(['지역'])['누적확진자','격리중','사망자','격리해제'].max().copy()
group = group.sort_values('누적확진자', ascending=False)
group.style.background_gradient(cmap='Pastel1_r')
Out[39]:
누적확진자 격리중 사망자 격리해제
지역
대구 7253 6144 61 1048
경북 1438 1178 25 235
경기 339 277 3 59
서울 325 270 0 55
부산 166 107 1 58
충남 134 118 0 16
경남 109 86 0 23
세종 41 41 0 0
울산 39 30 0 9
강원 39 30 1 8
인천 37 32 0 5
충북 37 32 0 5
대전 26 22 0 4
광주 23 17 0 6
전북 14 9 0 5
전남 7 5 0 2
제주 6 4 0 2

지역별 사망자

  • 사망자는 총 91명으로 대구, 경북, 경기, 강원, 부산 순으로 많다.
  • 경기 3명, 강원 1명을 제외하면 전부 경상도이다.
In [40]:
deaths = korea[['지역','사망자']]
deaths = deaths.sort_values(by='사망자',ascending=False).head(10)
deaths.style.background_gradient(cmap='Reds')
Out[40]:
지역 사망자
5 대구 61
3 경북 25
1 경기 3
0 강원 1
7 부산 1
11 인천 0
15 충남 0
14 제주 0
13 전북 0
12 전남 0

격리해제가 없는 지역

  • 아직까지 격리해제가 없는 지역을 알아보았다.
  • 지역은 세종으로 확진자 41명 모두 격리 중이다.
In [41]:
no_recovered = korea[korea['격리해제'] == 0]
no_recovered = no_recovered.sort_values('누적확진자', ascending=False)
no_recovered.style.background_gradient(cmap='Reds')
Out[41]:
지역 누적확진자 격리중 사망자 격리해제
9 세종 41 41 0 0

모든 확진자가 격리해제된 지역

  • 모든 확진자가 격리해제된 지역은 없다.
In [42]:
all_recovered = korea[korea['누적확진자'] == korea['격리해제']]
all_recovered = all_recovered.sort_values('누적확진자', ascending=False)
all_recovered.style.background_gradient(cmap='Greens')
Out[42]:
지역 누적확진자 격리중 사망자 격리해제

지역별 확진자, 사망자 시각화

In [43]:
fig = px.bar(korea[['지역','누적확진자']].
            sort_values('누적확진자', ascending=False),
            y='누적확진자', x='지역', color='지역',
            log_y=True, template='ggplot2',
            title='지역별 확진자수')
fig.show()

fig = px.bar(korea[['지역','사망자']].
            sort_values('사망자', ascending=False),
            y='사망자',x='지역', color='지역',
            log_y=True, template='ggplot2',
            title='지역별 사망자수')
fig.show()
In [44]:
base_url = 'http://ncov.mohw.go.kr/bdBoardList_Real.do?brdId=1&brdGubun=13&ncvContSeq=&contSeq=&board_id=&gubun='
response = requests.get(base_url)
soup = bs(response.text, 'html.parser')
table = soup.select('table')
table_html = str(table)
table_list = pd.read_html(table_html)
table_df = table_list[0]
table_df.columns = ['시도명', '전일대비확진자증감', '확진자수', '사망자수', '방생률', '일일검사건수']
table_df = table_df.drop(index=[0, 18], axis=0).reset_index().drop('index', axis=1)
In [45]:
geo_path = './CTPRVN_201905/TL_SCCO_CTPRVN_WGS84.json'
geo_json = json.load(open(geo_path, encoding='utf-8'))
geo_json['features'][0]['properties']['CTP_KOR_NM']
Out[45]:
'서울특별시'
In [46]:
sido = []
for n in range(17):
    a = geo_json['features'][n]['properties']['CTP_KOR_NM']
    a = str(a)
    sido.append(a)
    
table_df['sido'] = sido
geo_sido = pd.read_csv('data/전국위경도.csv', encoding='cp949')
df = pd.concat([table_df, geo_sido], axis=1)
In [47]:
df['patient_log'] = np.log(df['확진자수'])
df = df.rename(columns={'sido_x':'지역', 'patient_log':'확진자수(log)'})
In [48]:
fig = px.choropleth_mapbox(df, geojson=geo_json, color='확진자수(log)',
                           locations="sido", featureidkey="properties.CTP_KOR_NM",
                           center={"lat": 36.093370, "lon": 127.630479},
                           mapbox_style="carto-positron", zoom=6,
                           labels={'확진자수(log)':'코로나19 확진자수(log)'})
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
fig.show()

<국내 확진자 정보>

코로나바이러스감염증-19 (COVID-19) 국내 확진자 정보

  • patient_id : 번호
  • global_num : 질병관리본부에서 부여한 번호
  • sex : 성별
  • birth_year : 태어난 년도
  • age : 나이
  • country : 국가
  • province : 시/도
  • city : 군/구
  • disease : 기저질환 여부
  • infection_case : 감염 경로
  • infection_order : 감염 순서(ex:1차 감염)
  • infected_by : 어떤 환자로부터 감염되었는지
  • contact_number : 접촉한 횟수
  • symptom_onset_date : 증상이 시작된 날짜
  • confirmed_date : 확진 판정난 날짜
  • released_date : 격리해제된 날짜
  • deceased_date : 사망한 날짜
  • stat : 상태(격리/격리해제/사망)

patient 데이터 구조 확인

In [49]:
path = 'data/'
patient_data_path = path + 'Patientinfo.csv'
df_patient = pd.read_csv(patient_data_path)
df_patient = pd.read_csv('data/Patientinfo.csv')
df_patient.head()
Out[49]:
patient_id global_num sex birth_year age country province city disease infection_case infection_order infected_by contact_number symptom_onset_date confirmed_date released_date deceased_date state
0 1000000001 2 male 1964.0 50s Korea Seoul Gangseo-gu NaN overseas inflow 1.0 NaN 75.0 2020-01-22 2020-01-23 2020-02-05 NaN released
1 1000000002 5 male 1987.0 30s Korea Seoul Jungnang-gu NaN overseas inflow 1.0 NaN 31.0 NaN 2020-01-30 2020-03-02 NaN released
2 1000000003 6 male 1964.0 50s Korea Seoul Jongno-gu NaN contact with patient 2.0 2002000001 17.0 NaN 2020-01-30 2020-02-19 NaN released
3 1000000004 7 male 1991.0 20s Korea Seoul Mapo-gu NaN overseas inflow 1.0 NaN 9.0 2020-01-26 2020-01-30 2020-02-15 NaN released
4 1000000005 9 female 1992.0 20s Korea Seoul Seongbuk-gu NaN contact with patient 2.0 1000000002 2.0 NaN 2020-01-31 2020-02-24 NaN released

결측치 확인

In [50]:
df_patient.isna().sum()
Out[50]:
patient_id               0
global_num             952
sex                    291
birth_year             555
age                    297
country                  0
province                 0
city                   168
disease               2093
infection_case        1159
infection_order       2081
infected_by           1738
contact_number        1731
symptom_onset_date    1967
confirmed_date         269
released_date         1919
deceased_date         2083
state                    0
dtype: int64

데이터 전처리

  • 태어난 년도 숫자화
  • 확진일 datetime 형태로 바꾸기
  • 나이(ages) 변수 추가
  • 시각화와 분석을 위한 변수설정
In [51]:
df_patient['birth_year'] = df_patient.birth_year.fillna(0.0).astype(int)
df_patient['birth_year'] = df_patient['birth_year'].map(lambda val: val if val > 0 else np.nan)
df_patient.confirmed_date = pd.to_datetime(df_patient.confirmed_date)
df_patient['ages'] = 2020 - df_patient['birth_year'] 
df_patient.head()
Out[51]:
patient_id global_num sex birth_year age country province city disease infection_case infection_order infected_by contact_number symptom_onset_date confirmed_date released_date deceased_date state ages
0 1000000001 2 male 1964.0 50s Korea Seoul Gangseo-gu NaN overseas inflow 1.0 NaN 75.0 2020-01-22 2020-01-23 2020-02-05 NaN released 56.0
1 1000000002 5 male 1987.0 30s Korea Seoul Jungnang-gu NaN overseas inflow 1.0 NaN 31.0 NaN 2020-01-30 2020-03-02 NaN released 33.0
2 1000000003 6 male 1964.0 50s Korea Seoul Jongno-gu NaN contact with patient 2.0 2002000001 17.0 NaN 2020-01-30 2020-02-19 NaN released 56.0
3 1000000004 7 male 1991.0 20s Korea Seoul Mapo-gu NaN overseas inflow 1.0 NaN 9.0 2020-01-26 2020-01-30 2020-02-15 NaN released 29.0
4 1000000005 9 female 1992.0 20s Korea Seoul Seongbuk-gu NaN contact with patient 2.0 1000000002 2.0 NaN 2020-01-31 2020-02-24 NaN released 28.0
In [52]:
date_cols = ['confirmed_date','released_date','deceased_date']
for col in date_cols:
    df_patient[col] = pd.to_datetime(df_patient[col])
import math
def group_age(ages):
    if ages >= 0: # not NaN
        if ages % 10 != 0:
            lower = int(math.floor(ages / 10.0)) * 10
            upper = int(math.ceil(ages / 10.0)) * 10 - 1
            return f"{lower}-{upper}"
        else:
            lower = int(ages)
            upper = int(ages + 9) 
            return f"{lower}-{upper}"
    return "Unknown"


df_patient["age_range"] = df_patient["ages"].apply(group_age)
patient = df_patient
In [53]:
patient["time_to_release_since_confirmed"] = patient["released_date"] - patient["confirmed_date"]
patient["time_to_death_since_confirmed"] = patient["deceased_date"] - patient["confirmed_date"]
patient["duration_since_confirmed"] = patient[["time_to_release_since_confirmed", "time_to_death_since_confirmed"]].min(axis=1)
patient["duration_days"] = patient["duration_since_confirmed"].dt.days
age_ranges = sorted(set([ar for ar in patient["age_range"] if ar != "Unknown"]))
patient["state_by_gender"] = patient["state"] + "_" + patient["sex"]

격리 해제율, 사망률, 격리율

  • 격리 해제율 : 11.6%
  • 사망률 : 1.4%
  • 격리율 : 87%
  • 데이터의 최신화가 필요하다.
In [54]:
infected_patient = patient.shape[0]
rp = patient.loc[patient["state"] == "released"].shape[0]
dp = patient.loc[patient["state"] == "deceased"].shape[0]
ip = patient.loc[patient["state"]== "isolated"].shape[0]
rp=rp/patient.shape[0]
dp=dp/patient.shape[0]
ip=ip/patient.shape[0]
print("격리 해제율 : "+ str(rp*100) )
print("사망률 : "+ str(dp*100) )
print("격리율 : "+ str(ip*100) )
격리 해제율 : 11.553030303030303
사망률 : 1.4204545454545454
격리율 : 87.02651515151516

파이 플롯

In [55]:
states = pd.DataFrame(patient["state"].value_counts())
states["status"] = states.index
states.rename(columns={"state": "count"}, inplace=True)

fig = px.pie(states,
             values="count",
             names="status",
             title="환자들의 현재 상태",
             template="seaborn")
fig.update_traces(rotation=90, pull=0.05, textinfo="value+percent+label")
fig.show()
In [56]:
released = df_patient[df_patient.state == 'released']
isolated_state = df_patient[df_patient.state == 'isolated']
dead = df_patient[df_patient.state == 'deceased']

격리해제된 사람들의 연령대 분포

  • 연령대가 높아질수록 격리해제율이 낮아진다.
  • 20대에서 가장 높은 수치를 보이고 있다.
In [57]:
plt.figure(figsize=(10,6))
sns.set_style("darkgrid")
plt.title("Age distribution of the released")
sns.kdeplot(data=released['ages'], shade=True)
Out[57]:
<matplotlib.axes._subplots.AxesSubplot at 0x181ac54db00>

격리 중인 사람들의 연령대 분포

  • 격리 중인 환자는 20대와 50대가 가장 많다.
  • 20대는 격리해제가 많음에도 불구하고 격리 중인 사람도 많다.
  • 따라서 20대 확진자가 가장 많다고 볼 수 있다.
In [58]:
plt.figure(figsize=(10,6))
sns.set_style("darkgrid")
plt.title("Age distribution of the isolated")
sns.kdeplot(data=isolated_state['ages'], shade=True)
Out[58]:
<matplotlib.axes._subplots.AxesSubplot at 0x181bf3c75c0>

사망한 사람들의 연령대 분포

  • 연령대가 높아질수록 사망률이 높아진다.
  • 20대는 확진자가 많음에도 불구하고 사망자가 없다.
  • 이 분포를 통해 바이러스는 고령자에게 치명적인 질환이라고 볼 수 있다.
In [59]:
plt.figure(figsize=(10,6))
sns.set_style("darkgrid")
plt.title("Age distribution of the deceased")
sns.kdeplot(data=dead['ages'], shade=True)
Out[59]:
<matplotlib.axes._subplots.AxesSubplot at 0x181bfe59cf8>
In [60]:
male_dead = dead[dead.sex=='male']
female_dead = dead[dead.sex=='female']

사망자, 격리해제자. 격리자 분포

  • 분포의 위치가 확연히 다르다.
  • 이 분포를 통해 확진자는 20대 50대가 가장 많은 반면에, 사망자는 65세 이상 고령자가 많은 것을 확인할 수 있다.
In [61]:
sns.kdeplot(data=dead['ages'],label='deceased', shade=True)
sns.kdeplot(data=released['ages'],label='released', shade=True)
sns.kdeplot(data=isolated_state['ages'],label='isolated', shade=True)
Out[61]:
<matplotlib.axes._subplots.AxesSubplot at 0x181bfed1240>

성별별 사망한 사람의 연령대 분포

  • 성별로 나누어 확인해보았다.
  • 남성의 사망률이 여성의 사망률보다 높아 보인다.
In [62]:
plt.figure(figsize=(10,6))
sns.set_style("darkgrid")
plt.title("Age distribution of the deceased by gender")
sns.kdeplot(data=female_dead['ages'], label="Women", shade=True)
sns.kdeplot(data=male_dead['ages'],label="Male" ,shade=True)
Out[62]:
<matplotlib.axes._subplots.AxesSubplot at 0x181bff9f438>

성별별 확진자 수

In [63]:
fig = px.pie(values=df_patient.groupby(['sex']).size().values,
            names=df_patient.groupby(['sex']).size().index)
fig.update_layout(
    font=dict(
        size=15,
        color='#242323'
    )
    )

py.iplot(fig)

성별별 사망자 수

  • 확진자는 여성이 더 높지만 사망자는 남성이 더 높게 나왔다.
In [64]:
plt.figure(figsize=(15, 5))
plt.title('Sex')
dead.sex.value_counts().plot.bar()
Out[64]:
<matplotlib.axes._subplots.AxesSubplot at 0x181c0296b00>
In [65]:
death = pd.read_csv('data/deaths.csv')
data = death
data.loc[(data['나이'] > 0) & (data['나이'] < 20), '연령대'] = '10대'
data.loc[(data['나이'] >= 20) & (data['나이'] < 30), '연령대'] = '20대'
data.loc[(data['나이'] >= 30) & (data['나이'] < 40), '연령대'] = '30대'
data.loc[(data['나이'] >= 40) & (data['나이'] < 50), '연령대'] = '40대'
data.loc[(data['나이'] >= 50) & (data['나이'] < 60), '연령대'] = '50대'
data.loc[(data['나이'] >= 60) & (data['나이'] < 70), '연령대'] = '60대'
data.loc[(data['나이'] >= 70) & (data['나이'] < 80), '연령대'] = '70대'
data.loc[(data['나이'] >= 80) & (data['나이'] < 90), '연령대'] = '80대'
data.loc[(data['나이'] >= 90) & (data['나이'] < 100), '연령대'] = '90대'
data = data.drop(['번호','시도','사망일','나이'], axis=1)
data.head(5)
Out[65]:
성별 정신질환 고혈압 신질환 간질환 당뇨 심장질환 치매 뇌경색 ... 천식 폐질환 고지혈증 부정맥 심근경색 중풍 통풍 장기부전 뇌졸증 연령대
0 1.0 NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 60대
1 1.0 NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 50대
2 NaN 1.0 NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 40대
3 1.0 NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 50대
4 NaN NaN 1.0 NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN 50대

5 rows × 21 columns

In [66]:
pt = pd.pivot_table(data, index=['연령대','성별'], aggfunc='count').reset_index()
pt.columns[2:]
pt.head(5)
Out[66]:
연령대 성별 간질환 고지혈증 고혈압 기관지염 뇌경색 뇌졸증 당뇨 부정맥 ... 심근경색 심장질환 장기부전 정신질환 중풍 천식 치매 통풍 폐질환
0 30대 1 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 40대 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 50대 0 0 0 0 0 0 0 0 ... 0 0 0 0 3 0 0 0 0 0
3 50대 0 0 0 0 0 0 0 0 ... 0 0 0 0 1 0 0 0 0 0
4 60대 0 1 3 0 0 0 4 1 ... 1 0 3 0 3 0 0 0 0 0

5 rows × 21 columns

In [67]:
pt = pd.melt(pt, id_vars=['연령대','성별'])
pt.head(5)
Out[67]:
연령대 성별 variable value
0 30대 간질환 1
1 40대 간질환 0
2 50대 간질환 0
3 50대 간질환 0
4 60대 간질환 0
In [68]:
pt_data1=pd.pivot_table(pt, index=['연령대','성별','variable'], values='value', aggfunc='sum').reset_index()
pt_data1.loc[(pt_data1['value'] == 1) | (pt_data1['value'] == 2), 'variable'] = '기타'
pt_data1.loc[pt_data1['variable'] == '치매', 'variable'] = '정신질환'
pt_data1 = pt_data1.sort_values(['연령대','value'], ascending=[True, False])
pt_data1['value'] =pt_data1['value'].replace(0,np.nan)
pt_data1.dropna()
pt_data1.head(5)
Out[68]:
연령대 성별 variable value
0 30대 기타 1.0
1 30대 고지혈증 NaN
2 30대 고혈압 NaN
3 30대 기관지염 NaN
4 30대 뇌경색 NaN
In [69]:
px.bar(pt_data1, x='연령대', y='value', color='variable', barmode='stack',facet_col='성별'
       ,width=800,height=500 ,title='성별 기저질환과 기저질환수')
In [70]:
pt_data2 = pt_data1.groupby(['연령대'])['value'].agg(['sum','count']).reset_index()
pt_data2.columns
pt_data2['평균'] = pt_data2['sum'] / pt_data2['count']
px.line(pt_data2, x='연령대', y='평균', title='연령대별 평균 기저질환수')

성별과 연령대별 상태

In [71]:
age_gender_hue_order =["isolated_female", "released_female", "deceased_female",
                       "isolated_male", "released_male", "deceased_male"]
custom_palette = sns.color_palette("Reds")[3:6] + sns.color_palette("Blues")[2:5]

plt.figure(figsize=(12, 8))
sns.countplot(x = "age_range",
              hue="state_by_gender",
              order=age_ranges,
              hue_order=age_gender_hue_order,
              palette=custom_palette,
              data=patient)
plt.title("State by gender and age", fontsize=16)
plt.xlabel("Age range", fontsize=16)
plt.ylabel("Count", fontsize=16)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.legend(loc="upper right")
plt.show()

감염 경로

In [72]:
inf = pd.read_csv("data/감염경로.csv")
inf.head()
inf_drop = inf.drop(["case_id","province","city","group","latitude","longitude"] ,axis =1)
inf_drop
inf_g = inf_drop.groupby(["infection_case"])["confirmed"].sum()
inf_g
df = pd.DataFrame(inf_g)
df =df.reset_index()
df
ect = df[df["confirmed"]<=57]
ect["sum"] = ect["confirmed"].cumsum()
ect
e =df.drop([1,2,4,5,6,7,8,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,26,27,31,32],0)
e
e.loc[e["confirmed"] == 1589,'confirmed' ]=1589+399
e
Out[72]:
infection_case confirmed
0 Bonghwa Pureun Nursing Home 58
3 Cheongdo Daenam Hospital 122
9 Guro-gu Call Center 129
25 Shincheonji Church 5012
28 contact with patient 681
29 etc 1988
30 gym facility in Cheonan 105
In [73]:
fig = px.pie(e , values='confirmed' , names = 'infection_case')
fig.update_layout(
    title = "감염경로",
    font=dict(
        family="Arial, monospace",
        size=15,
        color="#7f7f7f"
    )
    )   
    
py.iplot(fig)

확진 후 격리해제 또는 사망으로 가는 시간

  • 격리해제는 보통 10일에서 16일이 걸리며 최대 25일이 걸린다.
  • 사망은 보통 1일에서 6일이 걸리며 최대 8일이 걸린다.
In [74]:
plt.figure(figsize=(12,8))
sns.boxplot(x='state',
           y='duration_days',
           order=['released','deceased'],
           data=patient)
plt.title('Time from confirmation to release or death',
         fontsize=16)
plt.xlabel('State', fontsize=16)
plt.ylabel('Days', fontsize=16)
plt.xticks(fontsize=12)
plt.yticks(fontsize=12)
plt.show()